Skip to content

Support Vitest 4 in @cloudflare/vitest-pool-workers#11632

Open
penalosa wants to merge 59 commits intomainfrom
penalosa/vitest-v4-support
Open

Support Vitest 4 in @cloudflare/vitest-pool-workers#11632
penalosa wants to merge 59 commits intomainfrom
penalosa/vitest-v4-support

Conversation

@penalosa
Copy link
Contributor

@penalosa penalosa commented Dec 12, 2025

Fixes #11064
Fixes #10260 (tests added to verify the mentioned libraries can be imported)
Fixes #9957 (provide data moved from HTTP headers to WebSocket messages, removing the ~8 KB size limit)
Fixes #8369 (defineWorkersConfig is removed)
Fixes #7795 (everything is now encapsulated in a plugin, and so this type of configuration problem is impossible)
Fixes #7717 (leaked FSWatcher from Pages ASSETS binding is now closed via AbortSignal on pool worker stop)
Fixes #7339 (we no longer recommend importing env from cloudflare:test. Instead, import env from cloudflare:workers and use withEnv to override the value: https://developers.cloudflare.com/workers/runtime-apis/bindings/#overriding-env-values)
Fixes #7324 (nodejs_compat_v2 and Node module flags are now forcibly enabled during tests)
Fixes #7288 (new WorkersSnapshotEnvironment proxies snapshot file operations over a service binding)
Fixes #6465 (workerd resolve condition is explicitly included, node condition is removed)
Fixes #6214 (bare Node.js specifiers like "url" are resolved by prepending node: in the module fallback)
Fixes #5592 (SSR optimizer replaced with server.deps.inline = true, so Stripe resolves to its worker build)
Fixes #5396 (module graph is now populated via standard Vitest PoolWorker RPC — the UI should work)

This a breaking change to the @cloudflare/vitest-pool-workers integration in order to support Vitest v4. Along with supporting Vitest v4 (and dropping support for Vitest v2 and v3), we've made a number of changes that may require changes to your tests. Our aim has been to improve stability & the foundations of @cloudflare/vitest-pool-workers as we move towards a v1 release of the package.

We've made a codemod to make the migration easier: wrangler codemod vitest-pool-v3-to-v4, which will make the required changes to your config file.

  • isolatedStorage & singleWorker: These have been removed in favour of a simpler isolation model that more closely matches Vitest. Storage isolation is now on a per test file basis, and you can make your test files share the same storage by using the Vitest flags --max-workers=1 --no-isolate
  • import { env, SELF } from "cloudflare:test": These have been deprecated in favour of import { env, exports } from "cloudflare:workers". exports.default.fetch() has the same behaviour as SELF.fetch(), except that it doesn't expose Assets. To test your assets, use the env.ASSETS bindings or write an integration test using startDevWorker() (recommended)
  • import { fetchMock } from "cloudflare:test": This has been removed. Instead, mock globalThis.fetch or use ecosystem libraries like MSW (recommended).

  • Tests
    • Tests included/updated
    • Tests not necessary because:
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: coming soon
  • Wrangler V3 Backport
    • Wrangler PR:
    • Not necessary because: not Wrangler

A picture of a cute animal (not mandatory, but encouraged)
IMG_20170827_223510_169 2

@changeset-bot
Copy link

changeset-bot bot commented Dec 12, 2025

🦋 Changeset detected

Latest commit: 6cce159

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-project-automation github-project-automation bot moved this to Untriaged in workers-sdk Dec 12, 2025
@penalosa penalosa force-pushed the penalosa/vitest-v4-support branch 4 times, most recently from 107ac9f to e018115 Compare December 17, 2025 22:22
@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 17, 2025

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@11632

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@11632

miniflare

npm i https://pkg.pr.new/miniflare@11632

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@11632

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@11632

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@11632

@cloudflare/vitest-pool-workers

npm i https://pkg.pr.new/@cloudflare/vitest-pool-workers@11632

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@11632

@cloudflare/workers-utils

npm i https://pkg.pr.new/@cloudflare/workers-utils@11632

wrangler

npm i https://pkg.pr.new/wrangler@11632

commit: 6cce159

workerdBuiltinModules.has(specifier))
) {
return this.socket.send(
// Tell Vitest to treat this module as "external" and load it using a workerd module import
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will then end up at our module fallback server

* hooks can be used to seed data. If this is disabled, all tests will share
* the same storage.
*/
isolatedStorage: z.boolean().default(true),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No more isolated storage

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The runtime limit was recently raised to 32MB, so we no longer need to chunk

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We no longer have a custom test runner

@penalosa penalosa force-pushed the penalosa/vitest-v4-support branch from e28ec0a to d45260d Compare December 18, 2025 15:10
@claude
Copy link
Contributor

claude bot commented Dec 18, 2025

Claude encountered an error —— View job


Reviewing Changesets

  • Read changeset guidelines
  • Review .changeset/many-fishes-raise.md
  • Review .changeset/wrangler-codemod-command.md
  • Check attached image for cute animals

@penalosa penalosa force-pushed the penalosa/vitest-v4-support branch from d45260d to 60332b7 Compare December 18, 2025 17:58
@penalosa penalosa force-pushed the penalosa/vitest-v4-support branch from b4ce401 to 964012e Compare December 18, 2025 19:24
@penalosa penalosa marked this pull request as ready for review December 18, 2025 21:47
@penalosa penalosa requested review from a team as code owners December 18, 2025 21:47
Copy link
Contributor

@ascorbic ascorbic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly some questions. Otherwise, looking great!

Comment on lines +162 to +168
d &&
typeof d === "object" &&
"m" in d &&
"a" in d &&
"i" in d &&
Array.isArray(d.a) &&
d.m === "fetch"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😬

Comment on lines 81 to 83
"@vitest/runner": "4.0.16",
"@vitest/snapshot": "4.0.16",
"vitest": "4.0.16"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a peerdep on a pinned version? Seems a bit strict!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4.0.16 is currently the only the version that is guaranteed to work. Previously we've updated the peer dep as we validate it on newer versions, but maybe that's a bit overkill

Comment on lines 82 to 84
const fromVitest =
/\/node_modules\/(\.store\/)?vitest/.test(callerFileName ?? "") ||
/\/packages\/vitest\/dist/.test(callerFileName ?? "");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The monkey-patching in this function seems really fragile. Is this unavoidable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately we do need to do it, in order to prevent Vitest doing IO at the top level & in order to register setTimeouts with waitUntil(). Despite how fragile it looks, it's been fairly okay across 3 major versions of Vitest.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That said, I've just had a chat with the runtime team, and I think we can get some changes landed in Q1 that will remove the need for this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

};
// Required by vitest/worker
// @ts-expect-error We don't actually implement `process.memoryUsage()`
process.memoryUsage = () => ({});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it's fine with the stub implementation? Could we upstream a fix to remove the requirement?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems fine, yeah. If possible, I'd like to get this in as-is and then work on upstreaming, since there are a few different things it would be good to get changed.


// Vitest needs this
// @ts-expect-error Apparently this is read-only
process.versions = { node: "20.0.0" };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we upstream a fix to not require it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above

devin-ai-integration[bot]

This comment was marked as resolved.

- Use workspace:* for @cloudflare/vitest-pool-workers (v4 compatible)
- Convert vitest config from defineWorkersConfig + poolOptions to cloudflareTest plugin
- Update test tsconfig types for v4 (@cloudflare/workers-types + /types export)
workerd's Windows SQLite code path in sqlite.c++:511 uses
kj::Path::toString() which produces Unix-style forward-slash paths
(e.g. D:/a/_temp/...), but passes nullptr VFS to sqlite3_open_v2()
which uses the win32 VFS expecting native paths. This causes
SQLITE_CANTOPEN. The fix is in workerd (toString -> toNativeString),
skip the test until then.
The __VITEST_POOL_WORKERS_RUNNER_DURABLE_OBJECT__ doesn't need
persistent state — it's just a singleton host for running tests.
Making it ephemeral avoids disk-backed SQLite, which hits a workerd
bug on Windows (sqlite.c++ uses Unix-style paths with the win32 VFS,
causing SQLITE_CANTOPEN for any project with DOs).
Ephemeral DOs don't support getByName() which the pool needs to
connect to the runner singleton via getDurableObjectNamespace().
The runner DO uses KV-backed storage (not SQLite) so it isn't
affected by the Windows SQLite path bug anyway.
workerd's Windows SQLite VFS uses kj::Path::toString() which produces
Unix-style forward-slash paths, causing SQLITE_CANTOPEN with the win32
VFS (cloudflare/workerd#6110). Instead of excluding SQLite DO fixtures
on Windows, use unsafeEphemeralDurableObjects to switch all DOs to
in-memory storage. This avoids disk SQLite paths entirely while keeping
DOs fully functional (including SQLite-backed ones). Cross-restart
persistence isn't needed for tests.
unsafeEphemeralDurableObjects doesn't work: inMemory storage doesn't
support enableSql (SQLite-backed DOs) and listDurableObjectIds reads
from disk. There's no miniflare-level workaround for the workerd
Windows SQLite path bug — disk-backed SQLite DOs require localDisk
storage. Re-add the durable-objects fixture exclusion on Windows
until workerd#6110 ships.
devin-ai-integration[bot]

This comment was marked as resolved.

@penalosa penalosa force-pushed the penalosa/vitest-v4-support branch from ebf3249 to 35ac491 Compare February 19, 2026 17:01
@penalosa penalosa force-pushed the penalosa/vitest-v4-support branch from 35ac491 to a8a1a8c Compare February 19, 2026 17:12
penalosa and others added 3 commits February 19, 2026 17:43
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…dead code

- Remove duplicate tailStream in WORKER_ENTRYPOINT_KEYS
- Fix cycle detection in getCjsNamedExports (seen.add wrong variable)
- Fix isEnabled() to match assertIsEnabled() semantics (disable flag precedence)
- Fix missing space in user-facing error message
- Fix SELF proxy crash on non-function properties
- Fix structuredSerializableStringify mutating caller's object
- Improve connectToMiniflareSocket error on WebSocket failure
- Add no-op methods to BroadcastChannel stub
- Fix off() to map Vitest event names to WebSocket events
- Fix garbled getSerializedOptions error message
- Remove TODO(now), commented-out code, stale comments
- Remove redundant ?? true after Zod default
- Add cross-referencing comments for shared magic strings
devin-ai-integration[bot]

This comment was marked as resolved.

- Fix prototype nesting in createProxyPrototypeClass (re-wraps on every construction)
- Fix off() unable to remove wrapped message/error listeners
- Close WebSocket explicitly in stop()
- Guard actionResults map against fetch failure leaks
- Avoid mutating caller objects in structuredSerializableStringify and send()
- Fix chunking test to use meaningful 1.2 MiB payload
- Fix console test no-op assertion and remove debug log
- Remove duplicate isolation test, clean up stale config options
- Add wrangler changeset for new codemod command
- Fix pnpm path in setTimeout caller detection regex
- Remove unused warnSpy variables in context-exports fixture
- Use semver check instead of startsWith for version guard
…nding (#7717)

generateAssetsFetch() in wrangler creates a chokidar watcher for _headers
and _redirects files that was never closed. This kept the Node.js event loop
alive, causing Vitest's "close timed out" warning on every test run.

Wire an AbortSignal through generateAssetsFetch() so the watcher can be
closed on demand. In vitest-pool-workers, track AbortControllers in a
module-level registry and abort them all in CloudflarePoolWorker.stop().
…ion and large provide data

- bare-specifiers.test.ts: import "url", "path", "buffer" without node: prefix (#6214)
- large-provide.test.ts: 50 KB provide/inject payload that exceeded v3 header limit (#9957)
- Reference-count active workers so watchers only close when the last
  worker stops, not when the first one does
- Move AbortController registration after the wrangler import so a
  failed import does not leak an orphaned controller
- Handle already-aborted signals in generateAssetsFetch
- Catch errors from watcher.close() to avoid unhandled rejections
Comment on lines 1 to 3
// Stub polyfill for node:v8 — workerd doesn't natively support this module.
// Vitest 4.1.0-beta.4 imports node:v8 to check `v8.startupSnapshot.isBuildingSnapshot()`
// which should always return false in workerd.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

…flags

Replace hand-written polyfills with workerd's native implementations:
- node:v8 stub → nodejs_v8_module compat flag
- process.memoryUsage/EventEmitter prototype → nodejs_process_v2 compat flag
- globalThis.Buffer → already provided by nodejs_compat_v2
- globalThis.process, process.argv, process.versions.node → already provided by nodejs_compat_v2/process_v2

Fix unhandled rejection in runDurableObjectAlarm (return → return await)
to prevent intermediate rejected promise with process_v2's proper
unhandledrejection tracking.

Fix request-mocking fixture: replace HttpResponse.error() with HTTP 400
to avoid MSW-internal TypeError escaping as unhandled rejection.
…dlers

Add `await` to `return` statements in handleRequest() that call async
sub-handlers (handleTokenExchange, updatePreviewToken, handleRawHttp).

Without `await`, when these functions throw synchronously (e.g. via
assertValidURL), the intermediate rejected promise is reported as an
unhandled rejection before the outer try/catch can handle it. This is
a workerd process_v2 behaviour difference vs Node.js — to be reported
upstream.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Untriaged

8 participants